/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ola_cloud_token_refresh.h"
#include <stddef.h>
#include "cJSON.h"
#include "securec.h"
#include "ola_def.h"
#include "ola_log.h"
#include "ola_base_utils.h"
#include "ola_coap_net.h"
#include "ola_error.h"
#include "ola_link.h"
#include "ola_cloud_def.h"
#include "ola_cloud_common.h"
#include "ola_cloud_token.h"
#include "ola_data.h"

static struct {
    unsigned long long lastRefreshTime; /* token刷新最后一次时间 单位：ms */
    unsigned long long waitRefreshTime; /* token刷新等待时间 单位：ms */
    unsigned int refreshCnt; /* token刷新计数 */
} g_all; /* 当前模块所有控制数据 */

static int CloudParseTokenRefreshResponse(const OlaData *payload)
{
    int ret;
    int timeout = 0;
    char *refreshToken = NULL;
    char *accessToken = NULL;
    cJSON *json = NULL;

    do {
        json = cJSON_Parse((const char *)payload->data);
        if (json == NULL) {
            OlaLogError("json parse err");
            ret = OLA_CJSON_ERR_PARSE;
            break;
        }
        ret = OlaGetJsonNumber(json, OLA_STRING_TIMEOUT, &timeout);
        refreshToken = OlaGetJsonString(json, OLA_STRING_REFRESH_TOKEN);
        accessToken = OlaGetJsonString(json, OLA_STRING_ACCESS_TOKEN);
        if ((ret == OLA_OK) && (refreshToken != NULL) && (accessToken != NULL) && (timeout >= 0)) {
            ret = CloudSetAccessToken(accessToken);
            if (ret != OLA_OK) {
                OlaLogWarning("set access token error");
                break;
            }
            ret = CloudSetRefreshToken(refreshToken);
            if (ret != OLA_OK) {
                OlaLogWarning("set refresh token error");
                break;
            }
            CloudSetTokenExpireTime((unsigned long long)timeout); /* 强转无问题 */
            /* timeout 时间剩余4/32 时启动刷新 */
            g_all.waitRefreshTime = CLOUD_TOKEN_REFRESH_TIMEVAL *
                (CloudGetTokenExpireTime() >> CLOUD_TOKEN_REFRESH_TIMEBASE);
            ret = OlaGetTime(&g_all.lastRefreshTime);
            if (ret != OLA_OK) {
                OlaLogWarning("get time error");
                break;
            }
            g_all.refreshCnt = 0;
        } else {
            OlaLogWarning("token or time parse error");
            break;
        }
    } while (false);

    cJSON_Delete(json);
    return ret;
}
static void CloudTokenRefreshResponseHandler(OlaCoapSession *session, const OlaCoapPacket *pkt)
{
    if (pkt == NULL) {
        OlaLogWarning("invalid param");
        return;
    }
    int ret;

    do {
        ret = CloudResponseCoapCheck(session, pkt);
        if (ret != OLA_OK) {
            OlaLogWarning("cloud token refresh response packet error");
            break;
        }
        ret = CloudResponseErrcodeHandler(&pkt->payload);
        if (ret != OLA_OK) {
            OlaLogWarning("cloud token refresh response errcode error");
            break;
        }
        ret = CloudParseTokenRefreshResponse(&pkt->payload);
        if (ret != OLA_OK) {
            OlaLogWarning("cloud token refresh response parse error");
            break;
        }
    } while (false);
}

static int CloudBuildTokenRefreshRequestData(OlaData *data)
{
    int ret = OLA_OK;
    char *payload = NULL;
    cJSON *json = NULL;
    char *refreshToken = NULL;

    do {
        json = cJSON_CreateObject();
        if (json == NULL) {
            OlaLogError("create json error");
            ret = OLA_CJSON_ERR_CREATE_OBJECT;
            break;
        }

        OlaData devid = { NULL, OLA_DEV_REGISTER_DEVID_LEN };
        if (OlaGetData(OLA_DATA_TYPE_DEV_ID, &devid) != OLA_OK) {
            OlaLogError("get devid error");
            ret = OLA_DATA_ERR_GET_DATA;
            break;
        }

        if ((devid.data == NULL) || (strlen((char *)devid.data) == 0) ||
            (strlen((char *)devid.data) > OLA_DEV_REGISTER_DEVID_LEN)) {
            OlaLogError("devid len error");
            ret = OLA_DATA_ERR_GET_DATA;
            break;
        }
        refreshToken = CloudGetRefreshToken();
        if (refreshToken == NULL) {
            OlaLogError("get refresh token empty");
            ret = OLA_ERROR;
            break;
        }
        if ((cJSON_AddStringToObject(json, OLA_STRING_DEVID, (char *)devid.data) == NULL) ||
            (cJSON_AddStringToObject(json, OLA_STRING_REFRESH_TOKEN, refreshToken) == NULL)) {
            OlaLogError("add string error");
            ret = OLA_CJSON_ERR_ADD_STRING;
            break;
        }
        payload = cJSON_PrintUnformatted(json);
        if (payload == NULL) {
            OlaLogError("get json payload error");
            ret = OLA_CJSON_ERR_PRINT_UNFORMATTED;
            break;
        }
        data->data = (unsigned char *)payload;
        data->len = strlen(payload);
    } while (false);

    cJSON_Delete(json);
    return ret;
}

static int CloudTokenRefreshRequest(void)
{
    int ret;
    OlaData sendData = {NULL, 0};
    OlaCoapParam param;

    do {
        ret = CloudBuildTokenRefreshRequestData(&sendData);
        if (ret != OLA_OK) {
            OlaLogWarning("cloud token refresh request send error");
            break;
        }
        (void)memset_s(&param, sizeof(OlaCoapParam), 0, sizeof(OlaCoapParam));
        OlaBuildCloudRequestCoapParam(&param, OLA_COAP_URI_TOKEN_REFRESH, CloudTokenRefreshResponseHandler, true);
        ret = OlaSendDataToCloud(&sendData, &param);
        if (ret != OLA_OK) {
            OlaLogWarning("cloud token refresh request send error");
            break;
        }
    } while (false);

    OlaDataFree(&sendData);
    return ret;
}

/*
 * 功能: token refresh模块初始化
 * 返回: 0，成功
 *       非0，错误码
 * 注意: 在进入上线前调用一次
 */
int CloudTokenRefreshInit(void)
{
    int ret;

    /* timeout 时间剩余4/32 时启动刷新 */
    g_all.waitRefreshTime = CLOUD_TOKEN_REFRESH_TIMEVAL * (CloudGetTokenExpireTime() >> CLOUD_TOKEN_REFRESH_TIMEBASE);
    g_all.refreshCnt = 0;
    ret = OlaGetTime(&g_all.lastRefreshTime);

    return ret;
}

/*
 * 功能: token refresh模块处理
 * 返回: 0，成功
 *       非0，错误码
 * 注意: 在上线状态调用
 */
int CloudTokenRefreshProcess(void)
{
    int ret;
    bool isTimeout = false;

    do {
        ret = OlaIsOvertime(&isTimeout, g_all.lastRefreshTime, g_all.waitRefreshTime);
        if (ret != OLA_OK) {
            OlaLogWarning("is overtime error");
            break;
        }
        if (isTimeout) {
            if (g_all.refreshCnt >= CLOUD_TOKEN_REFRESH_MAX_TIMES) {
                OlaLogWarning("cloud token refresh timeout");
                ret = OLA_CLOUD_ERR_TOKEN_REFRESH_TIMEOUT;
                break;
            }
            ret = CloudTokenRefreshRequest();
            if (ret != OLA_OK) {
                OlaLogWarning("send refresh token request error");
                break;
            }
            OlaLogInfo("cloud token refresh send cnt=%u", g_all.refreshCnt);
            ret = OlaGetTime(&g_all.lastRefreshTime);
            if (ret != OLA_OK) {
                OlaLogWarning("get time error");
                break;
            }
            /* 每 1/32 timeout 发送一次 refresh */
            g_all.waitRefreshTime = (CloudGetTokenExpireTime()) >> CLOUD_TOKEN_REFRESH_TIMEBASE;
            g_all.refreshCnt++;
        }
    } while (false);

    return ret;
}